<?php


namespace paySdk\core;


use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use paySdk\functions\postar\lib\Request\Body\Order\Order;
use paySdk\functions\postar\lib\Request\Body\Tag\Tag;
use paySdk\functions\postar\lib\Request\Header\Header;
use paySdk\functions\postar\lib\Webhook\Webhook;
use ReflectionException;

class BasePostar extends PaySdk
{
    private $host_arr = [
        "production" => "",
        "testing" => "http://testterpay.postar.cn"
    ];

    // 星驿付的平台公钥
    private $platform_public_key_path = "";
    // 统一下单接口的path
    protected $pay_url_path = "/yyfsevr/order/pay";
    // 获取银联用户标识的path
    protected $union_pay_tag_url_path = "/yyfsevr/order/getPaypalTag";

    // 请求头
    public $request_header = [];
    // 请求体
    public $request_body = [];

    // 待验签的参数数组，包含sign
    public $verify_param = [];

    // order或tag对象里属性名与接口入参的对应数组
    private $match_arr = [
        "agent_id" => "agetId", "customer_id" => "custId", "tx_amt" => "txamt", "tx_amt_order" => "txamtOrder",
        "device_no" => "driveNo", "lat" => "latitude", "lng" => "longitude", "client_ip" => "ip",
        "timestamp" => "timeStamp", "order_no" => "orderNo",
        // 微信
        "open_id" => "openid", "pay_way" => "payWay", "trade_type" => "traType",
        "wechat_app_id" => "wxAppid", "wechat_device_no" => "wxDeviceNo", "wechat_order_timeout" => "orderTimOut",
        "limit_pay" => "limitPay", "prd_order_no" => "prdOrderNo", "wechat_mini_program_id" => "userId",
        "wechat_access_code" => "code",
        // 支付宝
        "alipay_app_id" => "zfbappid", "alipay_buyer_account" => "zfbBuyerAccount", "stage_num" => "stagNum",
        // union pay
        "atq_tag" => "atqTag", "customer_login" => "custLogin", "user_auth_code" => "userAuthCode",
        "pay_code" => "payCode", "store_id" => "stoeId", "auth_code" => "authCode"
    ];

    /**
     * @param string $platform_public_key_path
     */
    public function setPlatformPublicKeyPath(string $platform_public_key_path)
    {
        $this->platform_public_key_path = $platform_public_key_path;
    }

    /**
     * BasePostar constructor.
     * @param Container $app
     * @throws Exception
     */
    public function __construct(Container $app)
    {
        parent::__construct($app);

        if (empty($this->app->params))
            throw new Exception("参数不能为空");

        $this->setPlatformPublicKey();
        if (isset($this->app->params['order']) || isset($this->app->params['tag']))
            $this->genRequestBody();
    }

    /**
     * 设置平台公钥
     * @throws Exception
     */
    private function setPlatformPublicKey() {
        $path = realpath(dirname(__FILE__));
        $class = strtolower(str_replace([__NAMESPACE__, "Base", "\\"], ["", "", ""], __CLASS__));
        $public_key_path =  "{$path}/../pem/{$class}/{$this->mode}_platform_public_key.pem";
        if (! file_exists($public_key_path))
            throw new Exception("平台公钥证书不存在");

        $this->platform_public_key_path = $public_key_path;
    }

    /**
     * 生成$this->request_body
     * @throws Exception
     */
    private function genRequestBody() {
        if (! isset($this->app->params['order']) && ! isset($this->app->params['tag']))
            throw new Exception("order与tag对象不能同时不能为空");

        if (isset($this->app->params['order'])) {
            $this->genRequestHeader();
            $this->parseParams();
        } else if(isset($this->app->params['tag'])) {
            $this->genRequestHeader();
            $this->parseParams('tag');
        }
    }

    /**
     * 设置$this->request_body
     */
    private function genRequestHeader() {
        $this->request_header = Header::HEADERS;
    }

    /**
     * @param string $type
     * @throws ReflectionException
     * @throws Exception
     */
    private function parseParams($type = "order") {
        $obj = null;
        $chk_param = false;
        if ($type === 'order') {
            $obj = $this->app->params['order'];
            if (! $obj instanceof Order)
                throw new Exception("非法订单对象");
        } else if ($type === 'tag') {
            $obj = $this->app->params['tag'];
            if (! $obj instanceof Tag)
                throw new Exception("非法标签对象");
        }

        if (false === $obj->__get("valid"))
            $obj->chkParam();

        // 通过反射获取到调用接口所需的参数，并将其押入数据，复制给$this->request_body
        $order_reflection = new \ReflectionClass($obj);
        $order_properties = $order_reflection->getProperties();
        array_walk($order_properties, function ($property) use(&$obj) {
            $property_name = $property->getName();
            if ($property->isProtected() === true && ! is_null($obj->__get($property_name)))
                $this->request_body[$this->matchKey($property_name)] = $obj->__get($property_name);
        });
    }

    /**
     * 把相关对象里的属性名按照接口的要求进行转化
     * @param $key
     * @return string
     */
    private function matchKey($key) {
        if (isset($this->match_arr[$key]) && !empty($this->match_arr[$key]))
            return $this->match_arr[$key];
        return $key;
    }

    /**
     * 签名
     * @throws Exception
     */
    public function signature()
    {
        // 将参数按照ASCII码升序排列后再按照url格式生成字符串$str_after_sort
        // 将$str_after_sort进行sha256加密，得到$str_after_sha256
        // 将$str_after_sha256用平台提供的共钥进行加密(是encrpyt方法，虽然文档中提到的是"签名"，但其实是加密，不是rsa的signature方法)
        ksort($this->request_body);
        $str_after_sort = urldecode(http_build_query($this->request_body));
        $this->log("按ASCII码将key按升序排列后的字符串（urldecode后的）: {$str_after_sort}");
        $str_after_sha256 = hash("sha256", $str_after_sort);
        $this->log("sha256后的字符串: {$str_after_sha256}");

        $rsa_operation = RsaOperation::getInstance($this->platform_public_key_path);
        $this->request_body["sign"] = $rsa_operation->publicEncrypt($str_after_sha256);
        $this->log("公钥加密后的字符串: {$this->request_body["sign"]}");

        // 若version为2.0.0
        if ($this->request_body["version"] === '2.0.0')
            var_dump("version: {$this->request_body["version"]}");

    }

    /**
     * 调用接口
     * @param string $url_path
     * @param string $method
     * @return string
     * @throws GuzzleException
     * @throws Exception
     */
    public function callApi(string $url_path, string $method = "POST") {
        $url = $this->host_arr[$this->mode] . $url_path;

        $options = [];
        if (!empty($this->request_header))
            $options["headers"] = $this->request_header;

        if ($method === "POST" && empty($this->request_body))
            throw new Exception("request_body不能为空");
        if (!empty($this->request_body)) {
            $options["json"] = $this->request_body;
        }

        $msg = "请求接口 url: {$url}, headers: " . json_encode($options["headers"]);
        if (isset($options["json"]))
            $msg .= ", body: " . json_encode($options["json"], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        $this->log($msg);
        $client = new Client();
        $resp = $client->request($method, $url, $options);

        if (200 !== $code = $resp->getStatusCode()) {
            $msg = "请求接口失败!! url: {$url}, headers: " . json_encode($options["header"]);
            if (isset($options["body"]))
                $msg .= ", body: " . $options["body"];
            $this->log($msg, "error");

            throw new Exception("接口请求失败");
        }

        return $resp->getBody()->getContents();
    }

    /**
     * 验签
     * @throws Exception
     */
    public function verify()
    {
        $this->parseVerifyParams();

        if (empty($this->verify_param))
            throw new Exception("待验签参数解析失败");

        if (! isset($this->verify_param['sign']))
            throw new Exception("签名不存在");
        $sign = $this->verify_param['sign'];
        unset($this->verify_param['sign']);

        ksort($this->verify_param);
        $str_after_sort = urldecode(http_build_query($this->verify_param));
        $this->log("把待验签参数的key按按ASCII码升序排列后的字符串（urldecode后的）: {$str_after_sort}");

        $str_after_sha256 = hash("sha256", $str_after_sort);
        $this->log("待验签字符串sha256后的字符串: {$str_after_sha256}");

        $rsa = RsaOperation::getInstance($this->platform_public_key_path);
        $original_string = $rsa->publicDecrypt($sign);
        $this->log("sign进行rsa公钥解密后的字符串: {$original_string}");

        if (false === hash_equals($original_string, $str_after_sha256))
            throw new Exception("签名验证失败");
    }

    /**
     * 解析异步回调参数
     * @throws ReflectionException
     */
    private function parseVerifyParams() {
        if (! $this->app->params['webhook'] instanceof Webhook)
            throw new Exception("非法webhook对象");

        // 通过反射获取到调用接口所需的参数，并将其押入数据，复制给$this->request_body
        $order_reflection = new \ReflectionClass($this->app->params['webhook']);
        $order_properties = $order_reflection->getProperties();
        array_walk($order_properties, function ($property) {
            $property_name = $property->getName();
            if ($property->isProtected() === true && ! is_null($this->app->params['webhook']->__get($property_name)))
                $this->verify_param[$property_name] = $this->app->params['webhook']->__get($property_name);
        });
    }
}